Project: Identify Customer Segments

In this project, you will apply unsupervised learning techniques to identify segments of the population that form the core customer base for a mail-order sales company in Germany. These segments can then be used to direct marketing campaigns towards audiences that will have the highest expected rate of returns. The data that you will use has been provided by our partners at Bertelsmann Arvato Analytics, and represents a real-life data science task.

This notebook will help you complete this task by providing a framework within which you will perform your analysis steps. In each step of the project, you will see some text describing the subtask that you will perform, followed by one or more code cells for you to complete your work. Feel free to add additional code and markdown cells as you go along so that you can explore everything in precise chunks. The code cells provided in the base template will outline only the major tasks, and will usually not be enough to cover all of the minor tasks that comprise it.

It should be noted that while there will be precise guidelines on how you should handle certain tasks in the project, there will also be places where an exact specification is not provided. There will be times in the project where you will need to make and justify your own decisions on how to treat the data. These are places where there may not be only one way to handle the data. In real-life tasks, there may be many valid ways to approach an analysis task. One of the most important things you can do is clearly document your approach so that other scientists can understand the decisions you've made.

At the end of most sections, there will be a Markdown cell labeled Discussion. In these cells, you will report your findings for the completed section, as well as document the decisions that you made in your approach to each subtask. Your project will be evaluated not just on the code used to complete the tasks outlined, but also your communication about your observations and conclusions at each stage.

Step 0: Load the Data

There are four files associated with this project (not including this one):

Each row of the demographics files represents a single person, but also includes information outside of individuals, including information about their household, building, and neighborhood. You will use this information to cluster the general population into groups with similar demographic properties. Then, you will see how the people in the customers dataset fit into those created clusters. The hope here is that certain clusters are over-represented in the customers data, as compared to the general population; those over-represented clusters will be assumed to be part of the core userbase. This information can then be used for further applications, such as targeting for a marketing campaign.

To start off with, load in the demographics data for the general population into a pandas DataFrame, and do the same for the feature attributes summary. Note for all of the .csv data files in this project: they're semicolon (;) delimited, so you'll need an additional argument in your read_csv() call to read in the data properly. Also, considering the size of the main dataset, it may take some time for it to load completely.

Once the dataset is loaded, it's recommended that you take a little bit of time just browsing the general structure of the dataset and feature summary file. You'll be getting deep into the innards of the cleaning in the first major step of the project, so gaining some general familiarity can help you get your bearings.

Load the two datasets:

Display the number of rows and columns in the first dataset (azdias):

Display sample data from the first dataset (azdias):

Display the number of rows and columns from the second dataset (feat_info):

Display sample data from the second dataset (feat_info):

Tip: Add additional cells to keep everything in reasonably-sized chunks! Keyboard shortcut esc --> a (press escape to enter command mode, then press the 'A' key) adds a new cell before the active cell, and esc --> b adds a new cell after the active cell. If you need to convert an active cell to a markdown cell, use esc --> m and to convert to a code cell, use esc --> y.

Step 1: Preprocessing

Step 1.1: Assess Missing Data

The feature summary file contains a summary of properties for each demographics data column. You will use this file to help you make cleaning decisions during this stage of the project. First of all, you should assess the demographics data in terms of missing data. Pay attention to the following points as you perform your analysis, and take notes on what you observe. Make sure that you fill in the Discussion cell with your findings and decisions at the end of each step that has one!

Step 1.1.1: Convert Missing Value Codes to NaNs

The fourth column of the feature attributes summary (loaded in above as feat_info) documents the codes from the data dictionary that indicate missing or unknown data. While the file encodes this as a list (e.g. [-1,0]), this will get read in as a string object. You'll need to do a little bit of parsing to make use of it to identify and clean the data. Convert data that matches a 'missing' or 'unknown' value code into a numpy NaN value. You might want to see how much data takes on a 'missing' or 'unknown' code, and how much data is naturally missing, as a point of interest.

As one more reminder, you are encouraged to add additional cells to break up your analysis into manageable chunks.

Define function to calculate the number of NaN data values in a dataset:

Display the number of missing or unknown data values:

Convert the missing or unknown data to NaNs and remove from the dataset:

Display the number of missing or unknown data after the conversion:

Step 1.1.2: Assess Missing Data in Each Column

How much missing data is present in each column? There are a few columns that are outliers in terms of the proportion of values that are missing. You will want to use matplotlib's hist() function to visualize the distribution of missing value counts to find these columns. Identify and document these columns. While some of these columns might have justifications for keeping or re-encoding the data, for this project you should just remove them from the dataframe. (Feel free to make remarks about these outlier columns in the discussion, however!)

For the remaining features, are there any patterns in which columns have, or share, missing data?

Display the name of each column in the dataset, along with the count of missing data in that column:

Display a chart to make it easier to understand the missing data:

As can be seen in the histogram above, the outliers start just before 200,000 missing data, outliers will defined as those with greater or equal to 200,000 missing data.

Generate the outlier columns and display them:

Remove the outlier columns from the azdias and feat_info datasets and display the number of rows and columns for each dataset:

As can be seen, 6 columns have been removed.

Discussion 1.1.2: Assess Missing Data in Each Column

The majority of columns have less than 200,000 missing values. From visual inspection of the chart, columns with more than or equal to 200,000 missing data are considered outliers, and have been removed:

A visual inspection reveals that there are several sets of columns with the same count of missing data:

Step 1.1.3: Assess Missing Data in Each Row

Now, you'll perform a similar assessment for the rows of the dataset. How much data is missing in each row? As with the columns, you should see some groups of points that have a very different numbers of missing values. Divide the data into two subsets: one for data points that are above some threshold for missing values, and a second subset for points below that threshold.

In order to know what to do with the outlier rows, we should see if the distribution of data values on columns that are not missing data (or are missing very little data) are similar or different between the two groups. Select at least five of these columns and compare the distribution of values.

Depending on what you observe in your comparison, this will have implications on how you approach your conclusions later in the analysis. If the distributions of non-missing features look similar between the data with many missing values and the data with few or no missing values, then we could argue that simply dropping those points from the analysis won't present a major issue. On the other hand, if the data with many missing values looks very different from the data with few or no missing values, then we should make a note on those data as special. We'll revisit these data later on. Either way, you should continue your analysis for now using just the subset of the data with few or no missing values.

Show the count of missing values for each row of the dataset:

A histogram will be displayed to show the number of rows versus the number of missing data.

The histogram has been enlarged to make sure that we can see all the plotted data. As can be seen, the number of missing data finishes just before half-way to 30. Outliers look to be just before 30 and end just before 50, so 25 looks to be just after the non-outlier data, and before the outlier data starts.

The data will be divided into two subsets: less than 25 missing data and more than (or equal to) 25 missing data.

Look at the distribution values for at least 5 columns where there are no or few missing data:

Display the columns that have no missing data:

Choose the first 5 columns where there are no missing data and show them:

Show charts to compare columns with many missing data versus those with no or few missing data:

Discussion 1.1.3: Assess Missing Data in Each Row

It can be seen from the charts above, that the distribution of data between the Few or No Missing Data and Many Missing Data look the same, although the scale is different. My conclusion is that dropping the Many Missing Data data will not affect the analysis.

Step 1.2: Select and Re-Encode Features

Checking for missing data isn't the only way in which you can prepare a dataset for analysis. Since the unsupervised learning techniques to be used will only work on data that is encoded numerically, you need to make a few encoding changes or additional assumptions to be able to make progress. In addition, while almost all of the values in the dataset are encoded using numbers, not all of them represent numeric values. Check the third column of the feature summary (feat_info) for a summary of types of measurement.

In the first two parts of this sub-step, you will perform an investigation of the categorical and mixed-type features and make a decision on each of them, whether you will keep, drop, or re-encode each. Then, in the last part, you will create a new data frame with only the selected and engineered columns.

Data wrangling is often the trickiest part of the data analysis process, and there's a lot of it to be done here. But stick with it: once you're done with this step, you'll be ready to get to the machine learning parts of the project!

Create a new dataset that contains the few_or_no_missing_data dataset:

Display a list of features and how many there are for each data type:

Step 1.2.1: Re-Encode Categorical Features

For categorical data, you would ordinarily need to encode the levels as dummy variables. Depending on the number of categories, perform one of the following:

Determine the categorical features and show them:

Remove columns that were removed earlier, then display the remaining columns:

Determine the binary features and display them:

Determine the multi-level features and display them:

Decide which binary feature is required to be re-encoded by displaying the unique values of each binary feature:

As can be seen above the OST_WEST_KZ values require re-encoding since they are not numbers.

Re-encode to keep this feature in the analysis:

Remove the multi-level features:

Display some sample data:

Discussion 1.2.1: Re-Encode Categorical Features

I re-encoded the values in OST_WEST_KZ as numbers to keep it in the analysis. I calculated, and kept the binary features ANREDE_KZ, GREEN_AVANTGARDE, SOHO_KZ, VERS_TYP, and OST_WEST_KZ. The multi-level features were calculated and then removed.

Step 1.2.2: Engineer Mixed-Type Features

There are a handful of features that are marked as "mixed" in the feature summary that require special treatment in order to be included in the analysis. There are two in particular that deserve attention; the handling of the rest are up to your own choices:

Be sure to check Data_Dictionary.md for the details needed to finish these tasks.

Investigate PRAEGENDE_JUGENDJAHRE and create two new columns:

Display the rows x columns to show that 2 new columns have been added:

Investigate CAMEO_INTL_2015 and create two new columns:

Display the rows x columns to show that 2 new columns have been added:

Discussion 1.2.2: Engineer Mixed-Type Features

The two mixed-value features PRAEGENDE_JUGENDJAHRE and CAMEO_INTL_2015 were kept. For each of these mixed-value features I created two new feature columns, copied values from the initial mixed-value feature column, created two dictionaries to map initial feature values to new feature values, and performed a replace operation using the dictionaries on the new feature columns. The remaining mixed-values features will be removed.

Step 1.2.3: Complete Feature Selection

In order to finish this step up, you need to make sure that your data frame now only has the columns that you want to keep. To summarize, the dataframe should consist of the following:

Make sure that for any new columns that you have engineered, that you've excluded the original columns from the final dataset. Otherwise, their values will interfere with the analysis later on the project. For example, you should not keep "PRAEGENDE_JUGENDJAHRE", since its values won't be useful for the algorithm: only the values derived from it in the engineered features you created should be retained. As a reminder, your data should only be from the subset with few or no missing values.

Remove the mixed features and then display sample data to show only the necessary columns exist:

Step 1.3: Create a Cleaning Function

Even though you've finished cleaning up the general population demographics data, it's important to look ahead to the future and realize that you'll need to perform the same cleaning steps on the customer demographics data. In this substep, complete the function below to execute the main feature selection, encoding, and re-engineering steps you performed above. Then, when it comes to looking at the customer data in Step 3, you can just run this function on that DataFrame to get the trimmed dataset in a single step.

Here is the cleaning function.

Test out the cleaning function with the non-cleaned azdias_unclean dataset:

Display the number of rows and columns in the cleaned azdias_clean dataset:

Display the number of rows and columns in the newly cleaned df_test dataset:

Display the columns contained within the azdias_clean dataset:

Display the columns contained within the df dataset:

As can be seen from the previous tests, comparing the known clean azdias_clean dataset with the df_test dataset (built using the clean_data() function), shows them to have the same number of rows and columns, as well as the same columns. This shows that the clean_data() function is working.

Step 2: Feature Transformation

Step 2.1: Apply Feature Scaling

Before we apply dimensionality reduction techniques to the data, we need to perform feature scaling so that the principal component vectors are not influenced by the natural differences in scale for features. Starting from this part of the project, you'll want to keep an eye on the API reference page for sklearn to help you navigate to all of the classes and functions that you'll need. In this substep, you'll need to check the following:

Clean the dataset of all remaining NaN values:

Show the number of missing data to confirm whether any further cleaning is required:

Here the number of missing data is 0, so no further cleaning is required.

Apply the feature scaling to the data:

Discussion 2.1: Apply Feature Scaling

The data was cleaned prior to this step. Any remaining NaNs are cleaned via imputing. The missing data were replaced with the mean using the Imputer. StandardScaler was used to scale all features, so that its distribution will have a mean of 0 and a standard deviation of 1. The number of missing data was calculated to see if any further cleaning was required. The number of missing data was 0, so no further cleaning is required.

Step 2.2: Perform Dimensionality Reduction

On your scaled data, you are now ready to apply dimensionality reduction techniques.

Apply PCA to the dataset without any parameters as instructed:

Show a chart of the variance explained by each component:

Show a chart of the cumulative variance explained:

As can be seen from the above chart, when the number of principal components is approximately 60, the ratio of cumulative variance explained is around 0.95. So, the value that I will retain for the clustering part of the project is 60.

Re-apply the PCA using the number of components to retain; in this case 60:

Discussion 2.2: Perform Dimensionality Reduction

From the graph above, we see that using 60 as the number of components corresponds with approximately 95% cumulative variance explained.

Step 2.3: Interpret Principal Components

Now that we have our transformed principal components, it's a nice idea to check out the weight of each variable on the first few components to see if they can be interpreted in some fashion.

As a reminder, each principal component is a unit vector that points in the direction of highest variance (after accounting for the variance captured by earlier principal components). The further a weight is from zero, the more the principal component is in the direction of the corresponding feature. If two features have large weights of the same sign (both positive or both negative), then increases in one tend expect to be associated with increases in the other. To contrast, features with different signs can be expected to show a negative correlation: increases in one variable should result in a decrease in the other.

Define a function to determine PCA weights:

Map weights for the first principal component and display those weights:

Map weights for the second principal component and display those weights:

Map weights for the third principal component and display those weights:

Discussion 2.3: Interpret Principal Components

For each of the three principal components, I have listed the features with the top two positive correlations and the bottom two negative correlations. For each feature I have listed its description (as described in the data dictionary) and weight.

First principal component

Component: Data Dictionary: Weight:
PLZ8_ANTG3 Number of 6-10 family houses in the PLZ8 region. 0.232915
PLZ8_ANTG4 Number of 10+ family houses in the PLZ8 region. 0.225885

From the features that are positively correlated with the first principal component we see that the number of 6-10 family houses and 10+ houses increase when the first principal component increases.

Component: Data Dictionary: Weight:
PLZ8_ANTG1 Number of 1-2 family homes in the PLZ8 region. -0.232261
MOBI_REGIO Movement patterns. -0.247236

From the features that are negatively correlated with the first principal component we see that the number of 1-2 family homes and movement patterns decrease when the first principal component increases.

This principal component is about residential information and, to a lesser extent, movement patterns.

Second principal component

Component: Data Dictionary: Weight:
ALTERSKATEGORIE_GROB Estimated age based on given name analysis. 0.270977
FINANZ_VORSORGER Financial topology - be prepared. 0.251018

From the features that are positively correlated with the second principal component we see that the estimated age based on a given name analysis and a be prepared financial view increase when the second principal component increases.

Component: Data Dictionary: Weight:
FINANZ_SPARER Financial topology - money-saver. -0.257439
DECADE Generation by decade. -0.264504

From the features that are negatively correlated with the second principal component we see that a money-saver financial view and generation by decade decrease when the second principal component increases.

This principal component is about age and financial saving.

Third principal component

Component: Data Dictionary: Weight:
SEMIO_VERT Personality topology - dreamful. 0.353607
SEMIO_SOZ Personality topology - socially-minded. 0.291003

From the features that are positively correlated with the third principal component we see that the dreamful personality type and socially-minded personality type increase when the third principal component increases.

Component: Data Dictionary: Weight:
SEMIO_KAEM Personality topology - combative attitude. -0.360295
ANREDE_KZ Gender. -0.362642

From the features that are negatively correlated with the third principal component we see that a combative attitude personality type and gender decrease when the third principal component increases.

This principal component is about personality types and, to a lesser extent, gender.

Note: In general, all of the correlation values are considered, at best, weakly correlated to their respective principal component.

Step 3: Clustering

Step 3.1: Apply Clustering to General Population

You've assessed and cleaned the demographics data, then scaled and transformed them. Now, it's time to see how the data clusters in the principal components space. In this substep, you will apply k-means clustering to the dataset and use the average within-cluster distances from each point to their assigned cluster's centroid to decide on a number of clusters to keep.

Define a function to calculate the KMeans:

Run k-means clustering over a number of different cluster counts - 1 to 10 inclusive (note that this took just over 7 minutes to run on my desktop PC. Using the Udacity environment will mean it will take considerably longer):

Display a graph to visually obtain the cluster k value:

From visual inspection it looks like a good value for K is 7, since after 7, the distance between cluster counts look uniform.

Re-fit the K-mean model with the chosen K value (number of clusters), which is 7. Obtain the clusters prediction:

Display the newly obtained cluster prediction:

Discussion 3.1: Apply Clustering to General Population

K = 7 looks (via a visual inspection) to be the point at which the distance between cluster counts looks uniform. So, 7 clusters will be used.

Step 3.2: Apply All Steps to the Customer Data

Now that you have clusters and cluster centers for the general population, it's time to see how the customer data maps on to those clusters. Take care to not confuse this for re-fitting all of the models to the customer data. Instead, you're going to use the fits from the general population to clean, transform, and cluster the customer data. In the last step of the project, you will interpret how the general population fits apply to the customer data.

Load in the customer demographics data.

Use the previously defined clean_data function to clean the customer data:

Apply the scaler transformation:

Perform the PCA transformation:

Determine the customers clusters prediction:

Display the customers clusters prediction:

Step 3.3: Compare Customer Data to Demographics Data

At this point, you have clustered data based on demographics of the general population of Germany, and seen how the customer data for a mail-order sales company maps onto those demographic clusters. In this final substep, you will compare the two cluster distributions to see where the strongest customer base for the company is.

Consider the proportion of persons in each cluster for the general population, and the proportions for the customers. If we think the company's customer base to be universal, then the cluster assignment proportions should be fairly similar between the two. If there are only particular segments of the population that are interested in the company's products, then we should see a mismatch from one to the other. If there is a higher proportion of persons in a cluster for the customer data compared to the general population (e.g. 5% of persons are assigned to a cluster for the general population, but 15% of the customer data is closest to that cluster's centroid) then that suggests the people in that cluster to be a target audience for the company. On the other hand, the proportion of the data in a cluster being larger in the general population than the customer data (e.g. only 2% of customers closest to a population centroid that captures 6% of the data) suggests that group of persons to be outside of the target demographics.

Take a look at the following points in this step:

Create proportion data for the general population and customers data so that they can be compared:

Create a new dataset to hold the proportion data:

Calculate the proportion differences for each cluster and display that data:

Positive values indicate overrepresented clusters and negative values indicate underrepresented clusters. Based on the difference data, clusters 0, 2, 3, and 6 are overrepresented, whereas clusters 1, 4, and 5 are underrepresented.

Graph the proportion data so that the customer data and general population data can be easily compared:

From the graph it can be determined (by visual inspection) that cluster 6 is the most overrepresented cluster, and cluster 1 is the most underrepresented cluster. This is also confirmed by looking at the previously generated difference data and noting the largest positive value (most overrepresented) and the smallest negative value (most underrepresented).

Determine the kinds of people that are overrepresented in the customer data compared to the general population using cluster 6:

Determine the kinds of people that are underrepresented in the customer data compared to the general population using cluster 1:

Discussion 3.3: Compare Customer Data to Demographics Data

Observations:

Clusters 0, 2, 3, and 6 are popular (overrepresented) with the mail-order company. Cluster 6 is the most overrepresented.

Clusters 1, 4, and 5 are unpopular (underrepresented) with the mail-order company. Cluster 1 is the most underepresented.

Conclusions:

Looking at the first eight features (with feature definitions from the data dictionary) for cluster 6 shows that the people in this cluster tend to be young (less than 30), male, average to high in low financial interest, low to average in money saving, average to high in being prepared, very low to low in investing, very low to low in inconspicuousness, and low to average in home ownership.

Looking at the first eight features (with data definitions from the data dictionary) for cluster 1 shows that the people in this cluster tend to be close to middle age (30 - 45), male, low to average in low financial interest, average to high in money saving, low to average in being prepared, low to average in investing, low to average in inconspicuousness, and average to high in home ownership.

In summary, the data would infer that customers that are popular with the mail order company are those that are less inclined to make good financial decisions (in the present and the future), and are less financially sophisticated. Customers that are more financially sophisticated, and make better financial decisions for the present and the future are less popular with the mail order company.

Congratulations on making it this far in the project! Before you finish, make sure to check through the entire notebook from top to bottom to make sure that your analysis follows a logical flow and all of your findings are documented in Discussion cells. Once you've checked over all of your work, you should export the notebook as an HTML document to submit for evaluation. You can do this from the menu, navigating to File -> Download as -> HTML (.html). You will submit both that document and this notebook for your project submission.